#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <ucontext.h>
#include <signal.h>
#include <sys/time.h>
#include "threadStructure.h"
#include "fifoThread.h"
#include "thread.h"
#include "util.h"

boolean MAIN_IS_THREAD = FALSE;
thread * MAIN_THREAD;

static fifoThread fifo_READY ;//les threads non terminés
static fifoThread fifo_FINISH ;//les threads terminés
static fifoThread fifo_STOP ;//les threads qui attendent
                             //qu'un autre thread termine
 
/*
  Function: thread_self
  
  returns the tread ID
 */
thread_t thread_self(){
  //si le main n'est pas un thread il le devient sinon
  if(MAIN_IS_THREAD == FALSE && mainBecomesThread()==-1)
    return -1;
  return getRunningThreadTid();
}

/*
  Function:thread_create

  creates a new thread

  Parameters:
       t - the thread ID
       func-
       arg-

  Returns:
       an integer to report errors
 */

int thread_create(thread_t*t,void*(*func)(void *),void*arg){
#ifdef O_PREEMPTION
  interruptsOff();
#endif
  //si le main n'est pas un thread, il le devient sinon
  if(MAIN_IS_THREAD == FALSE && mainBecomesThread()==-1)
    return -1;

  //on cree le context du thread
  ucontext_t *uc = createContext((void (*) (void))func,arg);
  if(uc == NULL)
    return -1;

  //on cree le thread
  *t = getNextTID();
  thread *newth = newThread(*t, STATUS_READY, uc);

  //on met à jour la rélation thread pere et thread fils
  thread* pere = getRunningThread();
  pere->is_father = TRUE;
  newth->father = pere;
  newth->context->uc_link = pere->context;
  addInFifo(pere);

#ifdef O_PRIORITE
  updatePriority(pere);
#endif  

  //on lance le nouveau thread cree
  setRunningThread(newth);

#ifdef O_PREEMPTION
  interruptsOn();
#endif
  swapcontext(pere->context, newth->context);
  return 0;
}


/*
  Function:thread_yield

  forces the running thread to relinquish the thread
  until it again becomes the head of its thread list. 
 
  Returns:
       an integer to report errors
 */

int thread_yield(){

#ifdef O_PREEMPTION
  interruptsOff();
#endif
  
  //le thread courant
  thread *th1 = getRunningThread();
  //le prochain thread
  thread *th2 = prochainThread();

#ifdef O_PRIORITE
  updatePriority(th1);
#endif

#ifdef O_PREEMTION
  setAlarm();
#endif   
  if(th2->tid != th1->tid){
    addInFifo(th1);
    setRunningThread(th2);
#ifdef O_PREEMPTION
  interruptsOn();
#endif
    swapcontext(th1->context,th2->context);
  }
  return 0;
}

/*
  Function:thread_join

  suspends execution of the calling thread until
  the target thread terminates, unless the target 
  thread has already terminated  

  Parameters:
       thread - the thread ID
       retval - if!NULL the thread returned value
                will be stored in retval

  Returns:
       an integer to report errors
 */

int thread_join(thread_t identity, void **retval){

#ifdef O_PREEMPTION
  interruptsOff();
#endif
  //thread attendu
  thread* waited = retrouverLeThread(identity);

  //thread attendant
  thread* waiting = getRunningThread();

#ifdef O_PRIORITE
  updatePriority(waiting);
#endif  

  //si le thread à attendre n'existe pas
  if(!waited){
    return -1;
  }

  //si le thread à attendre attend aussi un autre thread
  if(waited->state == STATUS_STOP){
    return -1;
  }
  
  //on recupere l'adresse de la valeur de retour
  waited->adresseValRetour = retval;
  
  //on stocke dans la structure le thread qui attend
  waited->est_attendu = TRUE;
  waited->threadAttendant = waiting;
  
  //Si le thread a déjà terminé, on nettoie et on quitte
  if(waited->state == STATUS_FINISH){
    nettoyage_thread_termine(waited);
    return 0;
  }

  //sinon on lance le thread
  waiting->state = STATUS_STOP;
  addInFifo(waiting);
  setRunningThread(waited);

  swapcontext(waiting->context, waited->context);
  return 0;
}

/*
  Function:thread_exit

  terminates the calling thread

  Parameters:
       retval - available to any successful join 
                with the terminating thread
  Returns:
       an integer to report errors
 */

void thread_exit(void *retval){
#ifdef O_PREEMPTION
  interruptsOff();
#endif
  //le thread qui veut terminer son execution
  thread *courant = getRunningThread();

  //on recupere la valeur de retour
  courant->valRetour = retval;

  //on met le statut a FINISH
  courant->state = STATUS_FINISH;

#ifdef O_KILLCHILDREN
  //on tue tous les eventuels enfants du thread
  if(courant->is_father == TRUE){
    killThreadChildren(courant->tid);
  }
#endif 
  
  //si le thread est attendu par un autre thread
  if(courant->est_attendu == TRUE){
    thread* waiting = courant->threadAttendant;
  
    //on fait le nettoyage
    nettoyage_thread_termine(courant);
  
    //on lance le thread qui attendait que le thread termine
    removeThreadByTid(&fifo_STOP,waiting->tid);    
    waiting->state = STATUS_READY;
    setRunningThread(waiting);
#ifdef O_PREEMPTION
  interruptsOn();
#endif
    setcontext(waiting->context);
    exit(1);
  }
 
  //sinon on lance le prochain thread
  thread* prochain = prochainThread();
  if(prochain->tid != courant->tid){
    addInFifo(courant);
    setRunningThread(prochain);
#ifdef O_PREEMPTION
    interruptsOn();
#endif
    setcontext(prochain->context);
  }
  exit(1);
}

/*=============================*
 * FONCTIONS AUXILIIRES UTILES *
 *=============================*/

/*
 *Mets le thread dans la bonne pile
 */
void addInFifo(thread * th){
  if(!th)
    return;
  switch(th->state){
  case STATUS_READY:
    addFifoThread(&fifo_READY,th);
    break;
  case STATUS_FINISH:
    addFifoThread(&fifo_FINISH,th);
    break;
  case STATUS_STOP:
    addFifoThread(&fifo_STOP,th);
    break;
  default:
    perror("Statut invalide");
  }
}

int mainBecomesThread(){
  ucontext_t *uc = (ucontext_t*)malloc(sizeof(ucontext_t));
  MAIN_THREAD = newThread(getNextTID(), STATUS_READY, uc);
  if(!(uc && MAIN_THREAD))
    return -1;
  MAIN_IS_THREAD = TRUE;
  setRunningThread(MAIN_THREAD);

#ifdef O_PREEMPTION
  static int hasBeenCalled = 0;
  if (!hasBeenCalled)
    scheduler();
  hasBeenCalled = 1;
#endif  

  return 0;
}

thread* retrouverLeThread(thread_t tid){
  if(estLeThreadCourant(tid))
    return getRunningThread();

  //on le cherche dans la file d'attente
  thread* th = removeThreadByTid(&fifo_READY,tid);
  if(th == NULL){
    //on le cherche dans la file des threads termines
    th = removeThreadByTid(&fifo_FINISH,tid);
  }
  if(th == NULL){
    //on le cherche dans la file des threads termines
    th = removeThreadByTid(&fifo_STOP,tid);
  }
  return th;//vaut NULL si pas trouve
}

thread* prochainThread(){
  //si aucun thread n'attend
  if(isEmptyFifo(&fifo_READY))
    return getRunningThread();
  //sinon on ordonnance
#ifdef O_PRIORITE
  //ordonnancement: thread de plus grande priorité
  return removeBigPriority(&fifo_READY);
#else
  //ordonnancement: le premier de la liste d'attente
  return removeFifoHead(&fifo_READY);
#endif 
}

void killThreadChildren(thread_t tid){
  killThreadChildrenFromFIFO(tid,&fifo_READY);
  killThreadChildrenFromFIFO(tid,&fifo_STOP);
  killThreadChildrenFromFIFO(tid,&fifo_FINISH);
}

void nettoyage_thread_termine(thread* current){
  //on fait le lien entre la valeur de retour et son adresse
  //et on libere la memoire occupée par le thread
  if(current->adresseValRetour != NULL)
    *(current->adresseValRetour) = current->valRetour;
  free(current->context->uc_stack.ss_sp);
  free(current->context);
  free(current);
}

void killMainThread(){
  MAIN_IS_THREAD = FALSE;
  free(MAIN_THREAD->context);                    
  free(MAIN_THREAD);
}
